package com.such.kit.io.template;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import com.alibaba.fastjson.JSON;
import com.such.kit.Logger;
import com.such.kit.io.bean.ExcelIOConfig;
import com.such.kit.io.simple.ExcelIOSimple;
import com.such.kit.io.source.DatabaseIOSource;
import com.such.kit.io.support.IOSource;
import com.such.kit.io.template.support.IOTemplate;
import com.such.kit.stream.StreamCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * [继承扩展于 {@link IOTemplate}] 的 Excel 类型 IO 工具类
 * TextIOTemplate 通过 {@link IOTemplate#templateIn 模板} {@link #initTemplate() 初始化} 构建 {@link ExcelIOConfig}
 * 内部实现基于 {@link ExcelIOSimple} 完成
 * 
 * ExcelIOTemplate 的模板介质为 Excel 文件，支持 xls、xlsx 类型的 Excel
 *   [警告] 模板文件的格式既为 {@link #fileType}，因此要求数据 Excel 类型与模板 Excel 类型一致
 * ExcelIOTemplate 的模板格式
 *   1. 在 Excel 中添加 Sheet 配置页，工作表名称为 {@link ExcelIOConfig#IO_C}
 *   2. 在 Sheet 配置页中选择一个单元格（建议从单元格 [1, 1] 开始），输入值 {@link ExcelIOConfig#IO_C}
 *   3. 为单元格添加注释，注释信息既为配置字符串，字符串应符合 JSON 标准
 *   4. 支持多组配置，重复步骤 2、3 便可
 * 
 * [举例] 以 {@link DatabaseIOSource} 为类
 * JSON
 * {
 *   {@link ExcelIOConfig#sheetName IO_SHEET_NAME}: "templateSheet", 
 *   {@link ExcelIOConfig#maxRow IO_MAX_ROW}: 2, 
 *   {@link ExcelIOConfig#dataArea IO_DATA_AREA}: 2, 
 *   {@link ExcelIOConfig#maxCol IO_MAX_COL}: 3, 
 *   {@link ExcelIOConfig#sheetSize IO_SHEET_SIZE}: 50000, 
 *   {@link ExcelIOConfig#inputExpression IO_INPUT_EXPRESSION}: ["insert into TABLE_INFO (schema, name, comment) value (:schema, :tableName, :tableComment)"], 
 *   {@link ExcelIOConfig#outputExpression IO_OUTPUT_EXPRESSION}: "select schema as schema, name as tableName, comment as tableComment from TABLE_INFO"
 * }
 * 
 * 数据
 * 用户名, 表名, 表说明
 * IO, TABLE1, TABLE1 COMMENT
 * IO, TABLE2, TABLE2 COMMENT
 * IO, TABLE3, TABLE3 COMMENT
 * 
 * [保留字] 列表
 *   {@link ExcelIOSimple#RESERVED_WORD_ROW_NUMBER}
 * </pre>
 */
public class ExcelIOTemplate extends IOTemplate {

	/** [参考 {@link ExcelIOSimple #fileType}] */
	private String fileType;
	private List<ExcelIOConfig> configs;
	private ExcelIOSimple ioSimple;

	/** [参考 {@link IOTemplate#IOTemplate(String)}] */
	public ExcelIOTemplate(String templatePath) throws Exception {
		super(templatePath);
	}

	/** [参考 {@link IOTemplate#IOTemplate(byte[])}] */
	public ExcelIOTemplate(byte[] templateBytes) throws Exception {
		super(templateBytes);
	}

	/** [参考 {@link IOTemplate#IOTemplate(InputStream)}] */
	public ExcelIOTemplate(InputStream templateIn) throws Exception {
		super(templateIn);
	}

	@Override
	protected void initTemplate() throws Exception {
		if (ValidateCommons.isEmpty(this.configs)) {
			this.configs = new ArrayList<ExcelIOConfig>();

			try {
				/* 
				 * 备份保存模板
				 * 当创建 HSSFWorkbook 失败（非 xls 格式），templateIn 将会被关闭
				 * 为了可以兼容 xlsx 格式，需要将 templateIn 进行备份，当创建 HSSFWorkbook 失败，使用备份 templateIn 创建 XSSFWorkbook
				 */
				byte[] templateInBackup = StreamCommons.binaryReader(super.templateIn);
				Workbook templateWorkbook = null;
				try {
					templateWorkbook = new HSSFWorkbook(new ByteArrayInputStream(templateInBackup));
					this.fileType = ExcelIOSimple.FILE_TYPE_XLS;
				} catch (Exception e) {
					Logger.info(getClass(), "创建 HSSFWorkbook 失败，尝试创建 XSSFWorkbook");
					// 兼容 xlsx
					templateWorkbook = new XSSFWorkbook(new ByteArrayInputStream(templateInBackup));
					this.fileType = ExcelIOSimple.FILE_TYPE_XLSX;
				}
				// 获取模板设置 sheet 页
				Set<String> configChecker = new HashSet<String>();
				Sheet ioConfigSheet = templateWorkbook.getSheet(ExcelIOConfig.IO_C);
				for (int i = 0; i < ioConfigSheet.getLastRowNum() + 1; i++) {
					Row configRow = ioConfigSheet.getRow(i);
					for (short j = 0; j < configRow.getLastCellNum() + 1; j++) {
						Cell configCell = configRow.getCell(j);
						if (ValidateCommons.isNotEmpty(configCell)) {
							String value = configCell.getStringCellValue();
							if (ExcelIOConfig.IO_C.equals(value)) {
								// 获取模板设置标识
								Comment comment = configCell.getCellComment();
								String configJson = comment.getString().toString();
								ExcelIOConfig config = JSON.parseObject(configJson, ExcelIOConfig.class);
								/*
								 * 对于每个 sheet 页的设置只能有一组 ExcelIOConfig
								 * 既不能有 sheetName 重复的 ExcelIOConfig
								 */
								String sheetName = config.getSheetName();
								if (!configChecker.contains(sheetName)) {
									config.setTemplateWorkbook(templateWorkbook);
									this.configs.add(config);

									configChecker.add(sheetName);
								}
							}
						}
					}
				}
			} finally {
				StreamCommons.close(super.templateIn);
			}

			this.ioSimple = new ExcelIOSimple(this.fileType, this.configs.toArray(new ExcelIOConfig[0]));
		}
	}

	@Override
	public void setCharset(String charset) {
		this.ioSimple.setCharset(charset);
	}

	@Override
	public void putArg(String key, Object value) {
		this.ioSimple.putArg(key, value);
	}

	@Override
	public void putArgs(Map<String, Object> args) {
		this.ioSimple.putArgs(args);
	}

	@Override
	public void clearArgs() {
		this.ioSimple.clearArgs();
	}

	@Override
	public String getFileType() {
		return this.fileType;
	}

	@Override
	public void input(IOSource ioSource, InputStream resourceIn) throws Exception {
		this.ioSimple.input(ioSource, resourceIn);
	}

	@Override
	public void output(IOSource ioSource, OutputStream resourceOut) throws Exception {
		this.ioSimple.output(ioSource, resourceOut);
	}

}
